#ifndef _DICOM_UPLOAD_HASH_CACHE_H_
#define _DICOM_UPLOAD_HASH_CACHE_H_

#include <string>
#include <gwbase/db/sqlite3_wrapper.h>
#include <gwbase/base/logging.h>
#include <gwbase/base/mutex.h>
#include <gwbase/base/util.h>
#include <gwbase/base/string_util.h>
#include "dicom/dicom_conf.h"

namespace dicom {

// You MUST NOT change any existing value
enum HashEntryStatus {
  HASH_INVALID = 0,

  // this hash&hexkey is create locally
  HASH_LOCAL = 100,

  // we downloaded a file with this hash&hexkey
  HASH_DOWNLOADED = 200,

  // this hash&hexkey is returned by isExists API
  // we'll do encryption locally to verify it, so this
  // is very trustful
  HASH_IS_EXISTS = 300,

  // we have upload a file using this hash&hexkey
  HASH_UPLOAD = 400,
};

// Hash entry is identified by <src_digest, hexkey> pair
struct HashEntry {
  // info of plain source file
  std::string src_md5;
  int64_t src_size;
  std::string src_digest;  // MD5+SHA1+SIZE
  std::string src_sha512;

  // info of encrypted file
  std::string hexkey;  // aes key
  int64_t enc_size;
  std::string enc_digest;  // SHA1

  // When update HashEntry, the lower status will not override
  // the higher status. That is, if an entry is already in
  // HASH_UPLOAD status, and we'll not insert a new entry with
  // HASH_LOCAL or HASH_IS_EXISTS status
  HashEntryStatus status;

  HashEntry() : src_size(-1), enc_size(-1), status(HASH_INVALID) {}

  bool IsComplete() const;
};

bool HashEntryEqual(const HashEntry& h1, const HashEntry& h2);

class HashCache {
 public:
  HashCache();
  ~HashCache();

  Status Open(const std::string& path);

  bool GetHashEntry(const std::string& src_md5, const std::string& hexkey,
                    HashEntry* result);

  // If <src_md5, hexkey> already exists, we only
  // update this entry if the new entry has a higher
  // status
  bool UpdateHashEntry(const HashEntry* entry);

  bool GetBestEntry(const std::string& src_md5, HashEntry* result);

 private:
  Status CreateTable();

  bool GetHashEntryWithLock(const std::string& src_digest,
                            const std::string& hexkey, HashEntry* result);
  bool LoadHashEntry(dicom::db::StmtPtr stmt, HashEntry* entry);
  bool InsertHashEntry(const HashEntry* entry);

  const DicomConf* conf_;
  dicom::db::DbPtr db_;
  Mutex mu_;
};

HashCache* OpenHashCache(const DicomConf* conf);

Status GetEncrytFileHash(HashCache* hcache, const std::string& path,
                         const std::string& src_md5, const std::string& hexkey,
                         HashEntry* hash);
}

#endif  // _DICOM_UPLOAD_HASH_CACHE_H_
